﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Collections.TreeCollections;
using Collections;
using FeatureModelCore.FMTypes;
using FeatureModelCore.FMInterface;
using FeatureModel.Collections.TreeArgs;
using FeatureModelCore.FeatureModelEventArgs;
using FeatureModelCore.FMCrossTreeConstraint;
using EventLogAssembly;
using System.Xml.Linq;
using FeatureModelCore.FMExceptions;

namespace FeatureModelCore.Optimized
{
    public class OptimizedFeatureModel : Tree
    {
        #region Properties
        public OptimizedGroupRelationship[] AllGroupRelationships { get { return getAllGroupRelationships(); } }
        public IOptimizableFeature[] AllNodes { get { return getAllTreeNodes(); } }
        public OptimizedConstraint[] AllCrossTreeConstraints
        {
            get { return this._constraints.ToArray<OptimizedConstraint>(); }
        }
        #endregion

        #region Fields
        /// <summary>
        /// Standard feature model
        /// </summary>
        private FeatureModelTree _fmTree;
        private FMSet<OptimizedConstraint> _constraints;
        #endregion

        #region Constructor
        /// <summary>
        /// Feature Model
        /// </summary>
        public OptimizedFeatureModel(FeatureModelTree fmTree)
            : base(null)
        {
            // storage
            _fmTree = fmTree;

            // cross tree constraints
            _constraints = new FMSet<OptimizedConstraint>();
            // add handler
            fmTree.TreeModifyCheck.AddHandler(validCrossTreeConstraint);
            fmTree.TreeModifyCompleted.AddHandler(optimizedTreeRefresh);
            ReloadFeatureModelTree(fmTree);
        }
        #endregion

        /// <summary>
        /// Optimized Tree Refresh
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void optimizedTreeRefresh(object sender, TreeEventArgs e)
        {
            // initial load tree
            XmlParsedEventArgs xmlParseArgs = e as XmlParsedEventArgs;
            if (xmlParseArgs != null)
            {
                ReloadFeatureModelTree(_fmTree);
                return;
            }

            #region Create & Add
            TreeObjectAddedEventArgs addArgs = e as TreeObjectAddedEventArgs;
            if (addArgs != null)
            {
                handleAddObjectEvent(addArgs);
                return;
            }

            TreeObjectCreatedEventArgs createArgs = e as TreeObjectCreatedEventArgs;
            if (createArgs != null)
            {
                handleCreateObjectEvent(createArgs);
                return;
            }

            ConstraintCreatedEventArgs conCreatedArgs = e as ConstraintCreatedEventArgs;
            if (conCreatedArgs != null)
            {
                handleCreateConstraint(conCreatedArgs);
                return;
            }
            #endregion


            if (e is ConstraintModifiedEventArgs
                || e is RelationModifyArgs
                || e is ConstraintDeleteEventArgs
                || e is TreeObjectDeletedEventArgs)
            {
                ReloadFeatureModelTree(_fmTree);
                return;
            }
            #region Modify

            #endregion

            #region Delete
            #endregion

            //#region Tree Object Created
            //if (e is TreeObjectCreatedEventArgs)
            //{
            //    TreeObjectCreatedEventArgs e_ = (TreeObjectCreatedEventArgs)e;
            //    handleCreateObjectEvent(e_);
            //    return;
            //}
            //if (e is ConstraintCreatedEventArgs)
            //{
            //    ConstraintCreatedEventArgs e_ = (ConstraintCreatedEventArgs)e;
                
            //    this.CreateConstraint(e_.NewConstraintID.ToString(), e_.IdentifierA.ToString()
            //        , e_.IdentifierA.ToString(), e_.ConstraintType);
            //    return;
            //}
            //#endregion
            //#region Feature Name Modified
            //if (e is FeatureNameChangedEventArgs)
            //{
            //    FeatureNameChangedEventArgs e_ = (FeatureNameChangedEventArgs)e;
            //    FeatureTreeNode treeNode = (FeatureTreeNode)e_.Source;

            //    if (treeNode == null)
            //    {
            //        return;
            //    }
            //    IOptimizable obj = this[treeNode.ID];
            //    OptimizedFeature node = obj as OptimizedFeature;
            //    if (node != null)
            //    {
            //        node.FeatureName = e_.NewName;
            //        return;
            //    }

            //    foreach (OptimizedFeature n in AllNodes)
            //    {
            //        OptimizedFeatureNodeProperty p = n[treeNode.ID.ToString()];
            //        if (p != null)
            //        {
            //            p.Name = e_.NewName;
            //        }
            //    }

            //    return;
            //}
            //#endregion

        }
        #region Handlers
        /// <summary>
        /// Handle add event args
        /// </summary>
        /// <param name="e"></param>
        private void handleAddObjectEvent(TreeObjectAddedEventArgs e)
        {
            if (e.ObjectToAdd is FeatureTreeNode)
            {
                FeatureTreeNode treeNode = (FeatureTreeNode)e.ObjectToAdd;
                FeatureTreeGroupRelationship parent = (FeatureTreeGroupRelationship)e.Parent;
                this.CreateOptimizedFeature(parent, treeNode);
            }
            else if (e.ObjectToAdd is FeatureTreeGroupRelationship)
            {
                FeatureTreeGroupRelationship relation = (FeatureTreeGroupRelationship)e.ObjectToAdd;
                IFeature parent = (IFeature)e.Parent;
                this.CreateGroupRelationship(parent, relation);
            }
            return;
        }
        /// <summary>
        /// handle create object event
        /// </summary>
        /// <param name="e"></param>
        private void handleCreateObjectEvent(TreeObjectCreatedEventArgs e)
        {
            if (e.CreatedObject is FeatureTreeNode)
            {
                FeatureTreeNode treeNode = (FeatureTreeNode)e.CreatedObject;
                FeatureTreeGroupRelationship parent = (FeatureTreeGroupRelationship)e.Parent;
                this.CreateOptimizedFeature(parent, treeNode);
            }
            else if (e.CreatedObject is FeatureTreeGroupRelationship)
            {
                FeatureTreeGroupRelationship relation = (FeatureTreeGroupRelationship)e.CreatedObject;
                IFeature parent = (IFeature)e.Parent;
                this.CreateGroupRelationship(parent, relation);
            }
            return;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        private void handleCreateConstraint(ConstraintCreatedEventArgs e)
        {
            this.CreateConstraint(e.CreatedConstraint);
        }
        #endregion

        #region Parse to feature model
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public FeatureModelTree ToFeatureModelTree()
        {
            FeatureModelTree tree = new FeatureModelTree();
            FeatureTreeRoot root = (FeatureTreeRoot)tree.Root;
            OptimizedRootFeature optRoot = (OptimizedRootFeature)_root;

            root.FeatureName = optRoot.FeatureName;

            subToFeatureModelTree(tree, root, optRoot);
            // add cross tree contriant
            foreach (OptimizedConstraint cons in this._constraints)
            {
                TreeNodeIdentify IdA = mapId(tree, cons.NodeA.FeatureName);
                TreeNodeIdentify IdB = mapId(tree, cons.NodeB.FeatureName);
                tree.CreateConstraint(IdA, IdB, cons.Type);
            }

            return tree;

        }

        private TreeNodeIdentify mapId(FeatureModelTree fmTree, string featureName)
        {
            IFeature node = fmTree.GetFeatureNode(featureName);
            if(node != null)
            {
                return node.ID;
            }
            FeatureTreeRoot root = (FeatureTreeRoot)fmTree.Root;
            if (root.FeatureName == featureName)
            {
                return root.ID;
            }
            throw new Exception();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="fmTree"></param>
        /// <param name="?"></param>
        private void subToFeatureModelTree(FeatureModelTree fmTree, TreeObject obj, IOptimizable oObj)
        {
            if (obj is FeatureTreeRoot)
            {
                FeatureTreeRoot fmRoot = (FeatureTreeRoot)obj;
                OptimizedRootFeature node = (OptimizedRootFeature)oObj;

                foreach (OptimizedGroupRelationship oGr in node.Children)
                {
                    FeatureTreeGroupRelationship gr = fmTree.CreateGroupRelationship(
                        fmRoot, oGr.Type);
                    subToFeatureModelTree(fmTree, gr, oGr);
                }
                foreach (OptimizedPropertyForFeature oProp in node.PropertiesForFeature)
                {
                    string propertyName = string.Format("Feature: {0}", oProp.StandardObject.ID);
                    string propertyValue = ((OptimizedFeature)oProp.StandardObject).FeatureName;
                    FeatureNodeProperty prop = fmTree.CreateProperty(fmRoot, propertyName);
                    prop.PropertyValue = propertyValue;
                }

                foreach (OptimizedPropertyForRelationship oProp in node.PropertiesForRelation)
                {
                    string propertyName = string.Format("Feature: {0}", oProp.StandardObject.ID);
                    string propertyValue = ((OptimizedGroupRelationship)oProp.StandardObject).Type.ToString();
                    FeatureNodeProperty prop = fmTree.CreateProperty(fmRoot, propertyName);
                    prop.PropertyValue = propertyValue;
                }
            }
            else if (obj is FeatureTreeNode)
            {
                FeatureTreeNode fmNode = (FeatureTreeNode)obj;
                OptimizedFeature node = (OptimizedFeature)oObj;

                foreach (OptimizedGroupRelationship oGr in node.Children)
                {
                    FeatureTreeGroupRelationship gr = fmTree.CreateGroupRelationship(
                        fmNode, oGr.Type);
                    subToFeatureModelTree(fmTree, gr, oGr);
                }
                foreach (OptimizedPropertyForFeature oProp in node.PropertiesForFeature)
                {
                    string propertyName = string.Format("Feature: {0}", oProp.StandardObject.ID);
                    string propertyValue = ((OptimizedFeature)oProp.StandardObject).FeatureName;
                    FeatureNodeProperty prop = fmTree.CreateProperty(fmNode, propertyName);
                    prop.PropertyValue = propertyValue;
                }

                foreach (OptimizedPropertyForRelationship oProp in node.PropertiesForRelation)
                {
                    string propertyName = string.Format("Feature: {0}", oProp.StandardObject.ID);
                    string propertyValue = ((OptimizedGroupRelationship)oProp.StandardObject).Type.ToString();
                    FeatureNodeProperty prop = fmTree.CreateProperty(fmNode, propertyName);
                    prop.PropertyValue = propertyValue;
                }
            }
            else if (obj is FeatureTreeGroupRelationship)
            {
                FeatureTreeGroupRelationship fmGr = (FeatureTreeGroupRelationship)obj;
                OptimizedGroupRelationship oGr = (OptimizedGroupRelationship)oObj;

                foreach(OptimizedFeature oNode in oGr.ChildrenNodes)
                {
                    FeatureTreeNode node = fmTree.CreateFeatureNode(fmGr, oNode.FeatureName);
                    subToFeatureModelTree(fmTree, node, oNode);
                }
            }
        }
        #endregion


        #region Reload Optimized Feature Model
        /// <summary>
        /// Convert Feature Model Tree To Optimized Tree
        /// </summary>
        /// <param name="fmTree"></param>
        public void ReloadFeatureModelTree(FeatureModelTree fmTree)
        {
            // clear
            _objStorage.Clear();
            _constraints.Clear();

            FeatureTreeRoot fmRoot = (FeatureTreeRoot)fmTree.Root;
            _root = new OptimizedRootFeature((FeatureTreeRoot)fmTree.Root);
            _objStorage.Add(_root);

            subReloadFeatureModelTree(fmRoot);

            // add cross constraint
            foreach (CrossTreeConstraint con in fmTree.AllCrossTreeConstraints)
            {
                this.CreateConstraint(con);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        private void subReloadFeatureModelTree(TreeObject obj)
        {
            if (obj is FeatureTreeRoot)
            {
                FeatureTreeRoot root = (FeatureTreeRoot)obj;
                foreach (FeatureTreeGroupRelationship gr in root.ChildrenGroupRelationshipes)
                {
                    this.CreateGroupRelationship(root, gr);
                    subReloadFeatureModelTree(gr);
                }
            }
            else if (obj is FeatureTreeNode)
            {
                FeatureTreeNode node = (FeatureTreeNode)obj;
                foreach (FeatureTreeGroupRelationship gr in node.ChildrenGroupRelationshipes)
                {
                    this.CreateGroupRelationship(node, gr);
                    subReloadFeatureModelTree(gr);
                }
            }
            else if (obj is FeatureTreeGroupRelationship)
            {
                FeatureTreeGroupRelationship gRelationship = (FeatureTreeGroupRelationship)obj;
                foreach (FeatureTreeNode node in gRelationship.ChildrenNodes)
                {
                    this.CreateOptimizedFeature(gRelationship, node);
                    subReloadFeatureModelTree(node);
                }
            }
        }
        #endregion


        #region Create Feature Object
        /// <summary>
        /// Create Feature Node
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="rType"></param>
        /// <returns></returns>
        public void CreateOptimizedFeature(FeatureTreeGroupRelationship parent
            ,FeatureTreeNode treeNode)
        {
            // get group relationship node
            IOptimizable obj = (IOptimizable)this[parent.ID];
            OptimizedGroupRelationship gRNode = obj as OptimizedGroupRelationship;

            if (gRNode != null)
            {
                OptimizedFeature node = new OptimizedFeature(treeNode);

                this.addInstance(gRNode, node);
                return;
            }

            // get virtual group relationship node
            OptimizedPropertyForRelationship property = null;
            foreach (IOptimizableFeature node in AllNodes)
            {
                property = node.GetPropertyForRelation(parent.ID);
                if (property != null)
                {
                    OptimizedPropertyForFeature newP = new OptimizedPropertyForFeature(treeNode);
                    node.AddPropertyForFeature(newP);
                    return ;
                }
            }
            if (property == null)
            {
                throw new Exception();
            }
            return;
        }
        /// <summary>
        /// Create Group Relationship
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="gType"></param>
        /// <returns></returns>
        public void CreateGroupRelationship(IFeature parent
            , FeatureTreeGroupRelationship relation)
        {
            IOptimizable obj = (IOptimizable)this[parent.ID];
            IOptimizableFeature parentNode = GetTransferNodeInTree(this, parent.ID);
            if (parentNode == null)
            {
                throw new Exception();
            }

            if (relation.Type == FMTreeGroupRelationshipType.GroupMandatory)
            {
                OptimizedPropertyForRelationship newP = new OptimizedPropertyForRelationship(
                    relation);
                parentNode.AddPropertyForRelation(newP);

                return;
            }

            // add group relationship 
            OptimizedGroupRelationship gR = new OptimizedGroupRelationship
                (relation);
            this.addInstance((INonLeaf)parentNode, gR);
        }
        /// <summary>
        /// Create Cross Tree Constraint
        /// </summary>
        /// <param name="idA"></param>
        /// <param name="idB"></param>
        /// <param name="cType"></param>
        /// <returns></returns>
        public OptimizedConstraint CreateConstraint(CrossTreeConstraint cons)
        {
            string info = string.Format("Create optimized cross tree constraint '{0}' in tree '{1}'"
                , cons.ToString(), this.GetType().Name);
            OptimizedConstraintCreatedEventArgs e = new OptimizedConstraintCreatedEventArgs(
                this, info, 0, cons);
            PreProcess(this, e);

            OptimizedConstraint optCons = new OptimizedConstraint(this, cons);
            _constraints.Add(optCons);
            setCrossTreeConstraintInfo(optCons);

            return optCons;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="standardFeatureId"></param>
        /// <returns></returns>
        internal static IOptimizableFeature GetTransferNodeInTree(OptimizedFeatureModel tree, TreeNodeIdentify standardFeatureId)
        {
            IOptimizable obj = (IOptimizable)tree[standardFeatureId];
            IOptimizableFeature node = obj as IOptimizableFeature;
            if (node != null)
            {
                return node;
            }

            foreach (IOptimizableFeature n in tree.AllNodes)
            {
                OptimizedPropertyForFeature p = n.GetPropertyForFeature(standardFeatureId);
                if (p != null)
                {
                    return n;
                }
            }

            throw new Exception("No such node");
        }
        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cons"></param>
        private void setCrossTreeConstraintInfo(OptimizedConstraint cons)
        {
            TreeObject[] pathA = ((TreeObject)cons.NodeA).GetTreePath();
            TreeObject[] pathB = ((TreeObject)cons.NodeB).GetTreePath();

            // get the conjuction node
            CrossTreeConstraintInfoForNode info = null;
            int a = 0, b = 0;
            for (a = pathA.Length - 1, b = pathB.Length - 1;
                a >= 0 && b >= 0 && ReferenceEquals(pathA[a], pathB[b]); a--, b--)
            {
                IFeatureTreeObject fmObj = (IFeatureTreeObject)pathA[a];
                info = fmObj.ObjectExtension.addConstraint(cons, cons.NodeA, cons.NodeB, CrossTreeConstraintInfoType.Inner);
            }
            info.InfoType = CrossTreeConstraintInfoType.Current;

            for (; a >= 0; a--)
            {
                IFeatureTreeObject fmObj = (IFeatureTreeObject)pathA[a];
                info = fmObj.ObjectExtension.addConstraint(cons, cons.NodeA, null, CrossTreeConstraintInfoType.Outer);
            }

            for (; b >= 0; b--)
            {
                IFeatureTreeObject fmObj = (IFeatureTreeObject)pathB[b];
                info = fmObj.ObjectExtension.addConstraint(cons, null, cons.NodeB, CrossTreeConstraintInfoType.Outer);
            }
        }

        #region Get Optimized Feature Objects
        /// <summary>
        /// get all group relationship
        /// </summary>
        /// <returns></returns>
        private OptimizedGroupRelationship[] getAllGroupRelationships()
        {
            List<OptimizedGroupRelationship> gRList = new List<OptimizedGroupRelationship>();
            foreach (IOptimizable obj in _objStorage)
            {
                if (obj is OptimizedGroupRelationship)
                {
                    gRList.Add((OptimizedGroupRelationship)obj);
                }
            }
            return gRList.ToArray<OptimizedGroupRelationship>();
        }
        /// <summary>
        /// Get all tree nodes
        /// </summary>
        /// <returns></returns>
        private IOptimizableFeature[] getAllTreeNodes()
        {
            List<IOptimizableFeature> nodeList = new List<IOptimizableFeature>();
            foreach (IOptimizable obj in _objStorage)
            {
                if (obj is IOptimizableFeature)
                {
                    nodeList.Add((IOptimizableFeature)obj);
                }
            }
            return nodeList.ToArray<IOptimizableFeature>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        private IOptimizable getTreeObject(TreeNodeIdentify id)
        {
            return getTreeObject(id.ToString());
        }
        /// <summary>
        /// Get Tree Object based on identify string
        /// </summary>
        /// <param name="idStr"></param>
        /// <returns></returns>
        private IOptimizable getTreeObject(string idStr)
        {
            foreach (IOptimizable obj in _objStorage)
            {
                if (obj.ID.ToString().Equals(idStr))
                {
                    return obj;
                }
            }
            return null;
        }
        #endregion


        private void getNodeParentChildRelationship(out IOptimizableFeature parent, out IOptimizableFeature child
            , IOptimizableFeature nodeA, IOptimizableFeature nodeB)
        {
            TreeObject[] nodeListA = ((TreeObject)nodeA).GetTreePath();
            TreeObject[] nodeListB = ((TreeObject)nodeB).GetTreePath();

            //OptimizedFeature[] nodeListA = getNodeParents(nodeA);
            //OptimizedFeature[] nodeListB = getNodeParents(nodeB);
            // get the conjuction node
            int a = 0, b = 0;
            for (a = nodeListA.Length - 1, b = nodeListB.Length - 1;
                a >= 0 && b >= 0 && ReferenceEquals(nodeListA[a], nodeListB[b]); a--, b--) ;

            if (a == 0 && b != 0)
            {
                parent = nodeA;
                child = nodeB;
            }
            else if (a != 0 && b == 0)
            {
                parent = nodeB;
                child = nodeA;
            }
            else
            {
                parent = null;
                child = null;
            }
        }

        #region Valid
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <returns></returns>
        private void validCrossTreeConstraint(Object sender, TreeEventArgs e)
        {
            ConstraintCreatedEventArgs args = e as ConstraintCreatedEventArgs;
            if (args == null)
            {
                return;
            }
            IOptimizableFeature nodeA = GetTransferNodeInTree(this, args.IdentifierA);
            IOptimizableFeature nodeB = GetTransferNodeInTree(this, args.IdentifierB);
            if (ReferenceEquals(nodeA, nodeB)
                && args.ConstraintType == FMCrossTreeConstraintType.Exclude)
            {
                throw new ConstraintCreationException();
            }
            if (validCrossTreeConstraint(nodeA, nodeB, args.ConstraintType) == false)
            {
                throw new ConstraintCreationException();
            }
        }
        /// <summary>
        /// Tree Node Identify
        /// </summary>
        /// <param name="nodeId">return false when there's error.
        /// return true when there's warning or it's correct</param>
        private bool validCrossTreeConstraint(IOptimizableFeature nodeA
            , IOptimizableFeature nodeB, FMCrossTreeConstraintType type)
        {
            IOptimizableFeature parentNode = null;
            IOptimizableFeature childNode = null;
            getNodeParentChildRelationship(out parentNode, out childNode
                , nodeA, nodeB);

            if (parentNode == null
                && childNode == null)
            {
                return true;
            }


            if (type == FMCrossTreeConstraintType.Exclude)
            {// exclude
                if (nodeA == nodeB)
                {
                    throw new Exception("Invalid feature model. Cannnot create such cross tree constraint.");
                }
                else
                {
                    string msg = string.Format("{0} would be the DEAD NODE. Cannot create such cross tree constraint."
                        , childNode.FeatureName);
                    throw new Exception(msg);
                }
            }
            // require
            if (nodeA == nodeB)
            {
                throw new Exception("The cross tree constraint is redudancy. Cannot create such cross tree constraint.");
            }
            else if(nodeA == parentNode)
            {
                string msg = string.Format("{0} will appears all products with {1}. "
                    + "It would cause other feature node to be dead node. Cannot create such cross tree constraint."
                    , nodeB.FeatureName, nodeA.FeatureName);
                throw new Exception(msg);
            }
            else if (nodeB == parentNode)
            {
                throw new Exception("The cross tree constraint is redudancy. Cannot create such cross tree constraint.");
            }

            return true;
        }
        /// <summary>
        /// Valid 
        /// </summary>
        /// <returns></returns>
        //public bool ValidAllCrossTreeConstraint()
        //{
        //    bool ret = true;
        //    bool tmp = false;

        //    foreach (OptimizedCrossTreeConstraint cons in _crossTreeConstraints)
        //    {
        //        tmp = validCrossTreeConstraint(cons);
        //        ret &= tmp;
        //    }
        //    return ret;
        //}
        #endregion

        public override object Clone()
        {
            throw new NotImplementedException();
        }
    }
}
